package client;

import interfaces.*;
import client.*;
import serveur.*;
import video.*;
import java.io.*;
import java.rmi.*;
import java.util.*;
import java.rmi.server.UnicastRemoteObject;
import java.lang.management.*;
import java.net.*;
import java.lang.*;
import javax.media.*;
import javax.media.protocol.*;
import javax.media.protocol.DataSource;
import javax.media.format.*;
import javax.media.control.TrackControl;
import javax.media.control.QualityControl;

public class ClientImpl extends UnicastRemoteObject implements ClientITF {

	Vector neighbourhood = new Vector();
	int id;
	int capacity;
	
	String host;
	int    port;
	
	Vector inbox   = new Vector();
	Vector sendbox = new Vector();

	ServeurITF serveur;

	public ClientImpl(String _host, int _port) throws RemoteException
	{
		id = -1;
		capacity = getCapacity();
		
		host = _host;
		port = _port;
	}
	
	public String getHost() throws RemoteException
	{
		return host;
	}
	
	public int getPort() throws RemoteException
	{
		return port;
	}
	
	/*
	   Méthodes de set/get classiques.
	*/
	
	public int getID() throws RemoteException
	{
		return id;
	}
	
	public void setID(int _id) throws RemoteException
	{
		id = _id;
	}
	
	public void setNeighbourhood(Vector _neighbourhood) throws RemoteException
	{
		neighbourhood.clear();
		for (ClientITF c : (Vector<ClientITF>) _neighbourhood)
		{
			neighbourhood.add(c);
		}
	}
	
	public Vector getNeighbourhood() throws RemoteException
	{
		return neighbourhood;
	}
	
	public int getCapacity() throws RemoteException
	{
		int capacity = (int) (ManagementFactory.getOperatingSystemMXBean().getSystemLoadAverage() * 100);
		return capacity;
	}
	
	public void setServer(ServeurITF _serveur) throws RemoteException
	{
		serveur = _serveur;
	}
	
	public ServeurITF getServer() throws RemoteException
	{
		return serveur;
	}
	
	public void addMail(MessageITF msg) throws RemoteException
	{
		inbox.add(msg);
	}
	
	public void removeMail(MessageITF msg) throws RemoteException
	{
		inbox.remove(msg);
	}
	
	public boolean equals(ClientITF client) throws RemoteException
	{
		return id == client.getID();
	}

	/*
	   Méthodes d'ajout/suppression d'un client à la liste de voisins.
	*/
	
	public void addNeighbour(ClientITF client) throws RemoteException
	{
		System.out.println("* Ajout d'un voisin ("+ client.getID() +")");
		neighbourhood.add(client);
		System.out.println("    Voisins: " + printNeighbours());
	} 
	
	public void removeNeighbour(ClientITF client) throws RemoteException
	{
		System.out.println("* Suppression d'un voisin ("+ client.getID() +")");
		neighbourhood.remove(client);
		System.out.println("    Voisins: " + printNeighbours());
	}

	/*
		Méthodes de connexion/déconnexion au serveur en dur si nécessaire.
	*/
	
	public ServeurITF connect_to(String server_host_port) throws Exception
	{
		ServeurITF server = null;
		
		System.out.println("* Tentative de connexion au serveur " + server_host_port);
		server = (ServeurITF) Naming.lookup("rmi://" + server_host_port + "/Serveur");
		
		if (server != null)
		{		
			serveur = server;
		
			// On se connecte au server
			serveur.connect(this);
		}
		
		return server;
	}
	
	public void disconnect() throws RemoteException
	{
		serveur.disconnect(this);
	}

	public int getNbMachines(ServeurITF serveur) throws RemoteException
	{
		return serveur.nbClients();
	}

	/*
		Méthodes sur les messages, ils seront rentrés au clavier
		ou entrés en dur.
	*/
	
	public MessageITF writeMail() throws RemoteException
	{
		BufferedReader keyboard = new BufferedReader(new InputStreamReader(System.in));
		String str = "";
		
		System.out.println("Message : ");
		
		try
		{
			str = keyboard.readLine();
		}
		catch (IOException e)
		{
			System.out.println(e);
		}
		
		MessageImpl msg = new MessageImpl(this, str);		
		
		return msg;
	}
	
	// Envoi du message à tous les voisins
	public void sendMail() throws RemoteException
	{
		MessageITF msg = writeMail();
		msg.addHaveSeen(this);

		for (ClientITF n : (Vector<ClientITF>) neighbourhood)
		{
			n.addMail(msg);
		}
	}
	
	// Envoi du message à tout le graphe
	public void forwardMail() throws RemoteException
	{
		if (checkMails())
		{
			for (MessageITF m : (Vector<MessageITF>) inbox)
			{
				for (ClientITF n : (Vector<ClientITF>) neighbourhood)
				{
					if (n.getID() != m.getIdClient())
					{
						m.addHaveSeen(this);
						n.addMail(m);
					}
				}
			}
			
			inbox.clear();
		}
	}
	
	public void checkBox() throws RemoteException
	{
		long timer;
		boolean time_flag;
		
		// Vérification régulière de l'inbox
		
		while (true)
		{
			timer = System.currentTimeMillis();
			time_flag = false;
		
			while (!time_flag)
			{
				time_flag = (System.currentTimeMillis() - timer > 400);
				try { Thread.sleep(200); } catch (Exception e) {}
			}
			
			checkMails();
		}			
	}
	
	public boolean checkMails() throws RemoteException
	{
		boolean flag = !inbox.isEmpty();
		
		if (flag)
		{
			for (MessageITF m : (Vector<MessageITF>) inbox)
			{
				System.out.println("Message : " + m.getText());
			}
		
			inbox.clear();
		}
		
		return flag;
	}
	
	public void answerMail(MessageITF msg) throws RemoteException
	{
		MessageITF answer = writeMail();
		ClientITF  asker  = (ClientITF) neighbourhood.elementAt(msg.getIdClient());
		asker.addMail(answer);
	}

	public String printNeighbours() throws RemoteException
	{
		String r = "";
		
		if (getNbNeighbours() == 0)
		{
			r = "[]";
		}
		else
		{
			for (ClientITF c : (Vector<ClientITF>) neighbourhood)
			{
				r += "["  + c.getID() + "] ";
			}
		}
		
		return r;
	}
	
	public String printGraph() throws RemoteException
	{
		return serveur.printGraph();
	}
	
	public int getNbNeighbours() throws RemoteException
	{
		return neighbourhood.size();
	}
	
	public void lancerElection() throws RemoteException
	{
		// Init		
		TokenITF jeton = new TokenImpl(this);

		// On passe le jeton à n'importe quel voisin (le premier)
		next(jeton, getNeighbourhood()).transfertJeton(jeton, this);
		
	}

	public void transfertJeton(TokenITF jeton, ClientITF from) throws RemoteException
	{
		System.out.println("Jeton reçu ...");
		
		// Si le message à déjà été vu
		if (jeton.hadBeenSeen(this))
		{
			// et qu'on est pas le demandeur initial
			if (getID() != jeton.getSender())
			{
				// On passe le jeton à un autre
				next(jeton, getNeighbourhood()).transfertJeton(jeton, this);
			}
			else // sinon on a fini notre demande
			{
				System.out.println("Fin d'élection - l'élu est " +
				                   jeton.getIdMax() + " (" +
				                   jeton.getCapacityMax() + ")");

				// On envoi le résultat au serveur
				serveur.resultatElection(jeton.getIdMax(), this);
				return;
			}
		}
		else
		{
			// On regarde si on a pas une meilleure capacité
			// meilleure capacité signifie capacité inférieure
			
			int capacity = getCapacity();
			System.out.println("moi : " + capacity + ", jeton : " + jeton.getCapacityMax());
			
			if (capacity <= jeton.getCapacityMax())
			{	
				jeton.setIdMax(this.getID());
				jeton.setCapacityMax(capacity);
			}
			jeton.addHaveSeen(this);
			
			// On passe le jeton à un autre
			next(jeton, getNeighbourhood()).transfertJeton(jeton, this);
		}
		
		System.out.println("Jeton renvoyé !");
	}
	
	public ClientITF next(TokenITF jeton, Vector<ClientITF> neighbours) throws RemoteException
	{
		// Méthode qui va chercher le prochain à qui envoyer le jeton
		ClientITF send_to;
		int index;
		
		// On cherche une personne dans ses voisins qui n'a pas eu le jeton		
		for (ClientITF n : (Vector<ClientITF>) neighbours)
		{
			if (!jeton.hadBeenSeen(n))
			{
				return n;
			}
		}
		
		// Si tout le monde l'a vu, on l'envoye à la personne
		// qui l'a reçu avant nous dans le addSeen
		index = jeton.getHaveSeen().indexOf(this);
		
		if (index > 0)
		{
			return (ClientITF) jeton.getHaveSeen().elementAt(index-1);
		}
		
		// Si on était en premier, on est le sender, ce qui revient à :		
		return jeton.getSenderITF();
	}

	public String[] choisiPar(ClientITF sender) throws RemoteException
	{
		// On donne les informations de l'élu au serveur	
		String[] ret   = new String[2];
		String address = new String();
		
		try
		{
			address = InetAddress.getLocalHost().getHostAddress().toString();
		}
		catch (Exception e) { System.out.println(e); }
		
		ret[0] = address;
		ret[1] = Integer.toString(port+1);
		
		return ret;
	}
	
	public void videoSend(String _ip, String _port) throws RemoteException
	{
		String[] video = new String[2];
		
		File f_video = new File("video/forman.mov");
		video[0] = _ip;
		video[1] = _port;
		
		System.out.println("* Transmission du flux vidéo ("+ f_video.getName() +")");
		
		VideoTransmit vt = new VideoTransmit(new MediaLocator("file://" + f_video.getAbsolutePath()),
		                                     video[0],
		                                     video[1]);
		
		// Démarrage de la transmission
		String result = vt.start();

		// Vérification des erreurs
		if (result != null)
		{
			System.err.println("Erreur : " + result);
			return;
		}
		
		System.out.println("* Démarrage de la transmission de 60 secondes");
		
		// On transmet pendant 25 secondes puis on éteint la transmission (sécurité)
		try
		{
			System.out.print("* Transmission en cours ...");
			
			for (int i=0; i<25; i++)
			{
				Thread.currentThread().sleep(1000);
				System.out.print(".");
			}
			
			System.out.println();
		}
		catch (InterruptedException ie) { System.out.println(ie); }

		vt.stop();

		System.out.println("* Fin de la transmission");
			
	}

	public void videoListen(ClientITF elu, String _ip, String _port) throws RemoteException
	{
		System.out.println("* Attente du flux vidéo ...");
		
		// On thread l'envoi de la vidéo
		VideoSendThread t = new VideoSendThread(elu, _ip, _port);
		t.start();
		
		System.out.println("* L'élu est au courant ...");
		
		String[] video = new String[1];
		video[0] = _ip + "/" + _port;
    	
		AVReceive2 avReceive = new AVReceive2(video);
		if (!avReceive.initialize())
		{
			System.err.println("Failed to initialize the sessions.");
			return;
		}

		System.out.println("* Réception en cours ...");

		// On attend que AVReceive se finisse
		try
		{
			while (!avReceive.isDone())
			{
				Thread.sleep(1000);
			}
		}
		catch (Exception e) {}
	
		System.out.println("* Réception du flux vidéo terminée");
    }
		
	
	
}
 
